#ifndef THIS_IS_BOOTLOADER
/*
  This file is part of the ArduinoRS485 library.
  Copyright (c) 2018 Arduino SA. All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include "Arduino.h"

#ifdef STM32F107xC
#include "stm32f1xx_ll_usart.h"
#elif defined(STM32H750xx)

#include <stm32h7xx_hal.h>
#include <stm32h7xx_ll_usart.h>

#endif
#ifdef STM32F1xx

#include "stm32f1xx_ll_usart.h"

#endif
#ifdef STM32F407xx

#include "stm32f4xx_ll_usart.h"

#endif
#ifdef STM32L4xx

#include <stm32l4xx_ll_usart.h>

#endif

#include "RS485.h"
#include "kSerial.h"

RS485Class::RS485Class(int8_t txPin, int8_t rx_pin, int8_t dePin, int8_t rePin) : kSerial(
        rx_pin, txPin),
                                                                                  _txPin(txPin),
                                                                                  _dePin(dePin),
                                                                                  _rePin(rePin),
                                                                                  _transmisionBegun(
                                                                                          false) {
}

int RS485Class::begin(unsigned int baud, u8 config, int rx_buff_sz, int tx_buff_sz) {
    if (_dePin > -1) {
        pinMode(_dePin, OUTPUT);
        digitalWrite(_dePin, LOW);
    }

    if (_rePin > -1) {
        pinMode(_rePin, OUTPUT);
        digitalWrite(_rePin, HIGH);
    }

    _transmisionBegun = false;

    return kSerial::begin(baud, config, rx_buff_sz, tx_buff_sz);
//    uart_attach_rx_callback((serial_t *) this, _rx_complete_irq);
//    uart_attach_tx_callback(&_serial, _tx_complete_irq);
}

int RS485Class::end() {
    kSerial::end();

    if (_rePin > -1) {
        digitalWrite(_rePin, LOW);
        // pinMode(_rePin, INPUT);
    }

    if (_dePin > -1) {
        digitalWrite(_dePin, LOW);
        // pinMode(_dePin, INPUT);
    }
    return 0;
}

int RS485Class::write(const u8 *buffer, int size) {
    if (!_transmisionBegun) {
        beginTransmission();
    }
    int size_intermediate;
    int ret = size;
    int available = availableForWrite();
    int available_till_buffer_end = data.tx_buf_size - _serial.tx_head;

    _written = true;
    if (isHalfDuplex()) {
        if (_rx_enabled) {
            _rx_enabled = false;
            uart_enable_tx(&_serial);
        }
    }

    // If the output buffer is full, there's nothing for it other than to
    // wait for the interrupt handler to free space
    while (!availableForWrite()) {
        // nop, the interrupt handler will free up space for us
    }

    // HAL doesn't manage rollover, so split transfer till end of TX buffer
    // Also, split transfer according to available space in buffer
    while ((size > available_till_buffer_end) || (size > available)) {
        size_intermediate = min(available, available_till_buffer_end);
        write(buffer, size_intermediate);
        size -= size_intermediate;
        buffer += size_intermediate;
        available = availableForWrite();
        available_till_buffer_end = data.tx_buf_size - _serial.tx_head;
    }

    // Copy data to buffer. Take into account rollover if necessary.
    if (_serial.tx_head + size <= data.tx_buf_size) {
        memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size);
        size_intermediate = size;
    } else {
        size_intermediate = data.tx_buf_size - _serial.tx_head;
        memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size_intermediate);
        memcpy(&_serial.tx_buff[0], buffer + size_intermediate,
               size - size_intermediate);
    }

    // Data are copied to buffer, move head pointer accordingly
    _serial.tx_head = (_serial.tx_head + size) % data.tx_buf_size;
    data.tx_count += size;

    // Transfer data with HAL only is there is no TX data transfer ongoing
    // otherwise, data transfer will be done asynchronously from callback
    if (!serial_tx_active(&_serial)) {
        // note: tx_size correspond to size of HAL data transfer,
        // not the total amount of data in the buffer.
        // To compute size of data in buffer compare head and tail
        _serial.tx_size = size_intermediate;
        uart_attach_tx_callback(&_serial, _tx_complete_irq, size_intermediate);
    }

    return ret;
}

void RS485Class::beginTransmission() {
    if (_dePin > -1) {
        digitalWrite(_dePin, HIGH);
        delayMicroseconds(20);
    }
    noReceive();
    _transmisionBegun = true;
}

u8 RS485Class::endTransmission() {
    // this->flush();

    if (_dePin > -1) {
        // delayMicroseconds(50);
        digitalWrite(_dePin, LOW);
        delayMicroseconds(60);
    }
    receive();
    _transmisionBegun = false;
    return 0;
}

void RS485Class::receive() {
    if (_rePin > -1) {
        digitalWrite(_rePin, LOW);
    }
    LL_USART_Disable(_serial.uart);
    LL_USART_EnableDirectionRx(_serial.uart);
    LL_USART_Enable(_serial.uart);
}

void RS485Class::noReceive() {
    if (_rePin > -1) {
        digitalWrite(_rePin, HIGH);
    }
    LL_USART_Disable(_serial.uart);
    LL_USART_DisableDirectionRx(_serial.uart);
    LL_USART_Enable(_serial.uart);
}

void RS485Class::sendBreak(unsigned int duration) {
    this->flush();
    this->end();
    pinMode(_txPin, OUTPUT);
    digitalWrite(_txPin, LOW);
    delay(duration);
    this->begin(data.baud, data.config);
}

void RS485Class::sendBreakMicroseconds(unsigned int duration) {
    this->flush();
    this->end();
    pinMode(_txPin, OUTPUT);
    digitalWrite(_txPin, LOW);
    delayMicroseconds(duration);
    this->begin(data.baud, data.config);
}

void RS485Class::setPins(int txPin, int dePin, int rePin) {
    _txPin = txPin;
    _dePin = dePin;
    _rePin = rePin;
}

RS485Class *get_serial_obj(serial_t *obj_s) {
//     serial_t *obj_s;
    RS485Class *obj;

//    obj_s = ( serial_t *)((char *)huart - offsetof(struct serial_s, handle));
    obj = (RS485Class *) ((char *) obj_s - offsetof(RS485Class, _serial));

    return (obj);
}

int RS485Class::_tx_complete_irq(serial_t *obj) {
    int remaining_data;
    // previous HAL transfer is finished, move tail pointer accordingly
    RS485Class *k = get_serial_obj(obj);
    obj->tx_tail = (obj->tx_tail + obj->tx_size) % k->data.tx_buf_size;

    // If buffer is not empty (head != tail), send remaining data
    if (obj->tx_head != obj->tx_tail) {
        remaining_data = (k->data.tx_buf_size + obj->tx_head - obj->tx_tail) % k->data.tx_buf_size;
        // Limit the next transmission to the buffer end
        // because HAL is not able to manage rollover
        obj->tx_size = min(remaining_data,
                           (int) (k->data.tx_buf_size - obj->tx_tail));
        uart_attach_tx_callback(obj, _tx_complete_irq, obj->tx_size);
        return -1;
    }
    k->endTransmission();
    k->_written = false;
    return 0;
}

// RS485Class RS485(SERIAL_PORT_HARDWARE, RS485_DEFAULT_TX_PIN, RS845_DEFAULT_DE_PIN, RS845_DEFAULT_RE_PIN);
#endif